package com.study.kt._03coroutines

import kotlinx.coroutines.*

/*
    kotlinx.coroutine包下的所有挂起函数都是可取消的
*  他们会检查协程的取消状态，当取消时就会抛出CancellationException
    如果协程正在处于计算过程中，并且没有检查取消状态，那么他是无法被取消的
* */
fun main_cancel() = runBlocking {
    val job = GlobalScope.launch {
        repeat(200) {
            println("$it run。。。")
            delay(500)
        }
    }
    delay(1100)
    println("main run。。。")

    // 取消job，但不是立即，期间肯定有些情况导致协程延迟结束
    job.cancel()
    // 讲协程添加到主任务，等待协程真正结束再执行后面的任务
    job.join()
    // 简便写法
    //job.cancelAndJoin()
    println("end")
}


fun main_cancelable() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        /*
         *  让计算代码变为可取消的
         * 1.周期性的调用一个挂起函数，该挂起函数会检查取消状态，比如使用yield函数
         * 2.显式的检查取消状态 isActive，对于长时间计算过程要使用isActive判断
        * */
        /* while (i < 20) {*/
        while (isActive) {
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job run ${i++}")
                nextPrintTime += 500
            }
        }
    }

    delay(1300)
    println("main run")
    // 协程使用while循环在cpu空轮转，属于计算任务，此时是无法取消协程的
    job.cancelAndJoin()
    println("end")
}

/*
*  使用finally关闭资源
* 1.join与cancelAndJoin都会等待所有的清理动作完成才会往下进行
* 2.finally表示协程执行完毕、要取消掉了，但如果在finally中执行了挂起函数 suspend、delay，会抛出CancellationException异常
*       因为运行着该代码块的协程已经被取消了，通常情况这种操作不会有问题，因为大多数关闭操作(取消job、关闭网络连接等)都是非阻塞的，并不需要挂起函数
* 但是如果某些情况需要在一个取消的协程中执行挂起操作，需要将这些函数放置到withContext中
*
* */
fun main_noncancelable() = runBlocking {
    val myJob = launch {
        try {
            repeat(100) {
                println("$it run....")
                delay(500)
            }
        } finally {
            // 不会执行
            /*delay(1000)
            println("finally end")*/
            // 此时正确运行finally end，实际上代码块中的程序使用了withContext和NonCancellable的上下文
            withContext(NonCancellable) {
                // NonCancellable保证以下代码块不会取消，一般和withContext共同使用
                println("finally run...")
                delay(1000)
                println("finally end")
            }

        }
    }

    delay(1000)
    println("main run...")
    myJob.cancelAndJoin()
    println("end")
}

/*
* 超时机制
*   如果一个协程被取消了，表示协程执行时间超过了某个设定值，我们可以使用Job的cancel取消
*   也可以使用内建函数使用超时取消
* */
fun main() = runBlocking {
    // 会抛出异常，使用try catch finally捕获，有些繁琐
    withTimeout(1900) {
        repeat(100) {
            println("hello,$it")
            delay(400)
        }
    }

    // 使用以下函数超时时不会抛出异常，而是返回null，更加友好
    val result = withTimeoutOrNull(1900) {
        repeat(100) {
            println("hello,$it")
            delay(400)
        }
        // 正常执行完毕，没有超时返回 end字符串
        "end"
    }
    // 此时result为null
    println("result is $result")
}